package com.tools.middle.elasticsearch;

import co.elastic.clients.elasticsearch._types.FieldSort;
import co.elastic.clients.elasticsearch._types.SortOptions;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregation;
import co.elastic.clients.elasticsearch.core.search.HighlightField;
import com.tools.common.container.MapEnum;
import com.tools.common.container.OwnPairs;
import com.tools.common.container.RowKit;
import com.tools.common.object.Note;
import com.tools.common.thread.Timeunit;

import java.util.*;

/**
 * ElasticSearch 搜索查询
 * 的配置项包装类的抽象实现
 * 该类的属性在多条件搜索时会失效（除了分词器）
 * */
@Note("ElasticSearch 搜索查询的配置项包装类的抽象实现。" +
        "该类的属性在多条件搜索时会失效（除了分词器）")
public abstract class Search {

    @Note("索引名称列表")
    private List<String> indicesNames;

    @Note("超时时间")
    private String timeout = "1s";

    @Note("本次查询所使用的分词器")
    private String analyzer;

    @Note("分页查询的偏移量")
    private int from = 0;

    @Note("分页查询每页条数")
    private int size = -1;

    @Note("【字段 - 排序规则】 列表")
    private List<SortOptions> sorts;

    @Note("source 过滤时，要显示的字段")
    private List<String> includeFields;

    @Note("source 过滤时，禁止显示的字段")
    private List<String> excludeFields;

    @Note("聚合函数集合")
    private Map<String, Aggregation> aggregations;

    @Note("高亮显示。key 是分组的组名称")
    private Map<String, HighlightField> highlights;

    /* **********************************************************************************
     *
     *          构造器
     *
     * *********************************************************************************
     * */

    Search() {}

    public Search(String indicesName) {
        this.setIndicesNames(indicesName);
    }

    public Search(String[] indicesNames) {
        this.setIndicesNames(indicesNames);
    }

    public Search(Iterable<String> indicesNames) {
        this.setIndicesNames(indicesNames);
    }

    /* **********************************************************************************
     *
     *          Getter / Setter
     *
     * *********************************************************************************
     * */

    List<String> getIndicesNames() {
        return indicesNames;
    }

    public List<String> getNewIndicesNames() {
        return (indicesNames == null || indicesNames.isEmpty())
                ? new ArrayList<>()
                : new ArrayList<>(indicesNames);
    }

    public void setIndicesNames(String indicesName) {
        default_indicesNameNotEmpty(indicesName);
        this.indicesNames = new ArrayList<>(1);
        this.indicesNames.add(indicesName);
    }

    public void setIndicesNames(String... indicesNames) {
        int length = default_getArrayLength(indicesNames);
        if(length == 0) return;
        this.indicesNames = new ArrayList<>(length);
        for (String i : indicesNames) {
            default_indicesNameNotEmpty(i);
            this.indicesNames.add(i);
        }
    }

    public void setIndicesNames(Iterable<String> indicesNames) {
        if(indicesNames instanceof List) {
            for (String f : indicesNames) default_fieldNotEmpty(f);
            this.indicesNames = (List<String>) indicesNames;
            return;
        }
        int size = default_getIterableSize(indicesNames);
        if(size != 0) {
            this.indicesNames = new ArrayList<>(size);
            for (String i : indicesNames) {
                default_indicesNameNotEmpty(i);
                this.indicesNames.add(i);
            }
        }
    }

    public String getTimeout() {
        return timeout;
    }

    public void setTimeout(String timeout) {
        if(timeout == null || timeout.isEmpty()) return;
        this.timeout = timeout;
    }

    public void setTimeout(int timeout, Timeunit timeunit) {
        if(timeunit == null) throw new NullPointerException("时间单位实例不能为 null");
        this.timeout = timeunit.getHasUnitTime(timeout);
    }

    public String getAnalyzer() {
        return analyzer;
    }

    public void setAnalyzer(String analyzer) {
        this.analyzer = analyzer;
    }

    public int getFrom() {
        return from;
    }

    public void setFrom(int from) {
        this.from = Math.max(from, 0);
    }

    public int getSize() {
        return size;
    }

    public void setSize(int size) {
        this.size = size;
    }

    List<SortOptions> getSorts() {
        return sorts;
    }

    public List<SortOptions> getNewSorts() {
        return (sorts == null) ? new ArrayList<>() : new ArrayList<>(sorts);
    }

    public void setSorts(String field, SortOrder order) {
        default_fieldNotEmpty(field);
        private_orderNotNull(order);
        this.sorts = new ArrayList<>(1);
        this.sorts.add(SortOptions.of(fn -> fn.field(f -> f.field(field).order(order))));
    }

    public void setSorts(Map<String, SortOrder> fieldAndOrders) {
        if(fieldAndOrders == null || fieldAndOrders.isEmpty()) return;
        this.sorts = new ArrayList<>(fieldAndOrders.size());
        for (Map.Entry<String, SortOrder> e : fieldAndOrders.entrySet()) {
            private_addSort(e.getKey(), e.getValue());
        }
    }

    public void setSorts(OwnPairs<String, SortOrder> fieldAndOrders) {
        if(fieldAndOrders == null || fieldAndOrders.isEmpty()) return;
        this.sorts = new ArrayList<>(fieldAndOrders.size());
        for (Map.Entry<String, SortOrder> e : fieldAndOrders.entrySet()) {
            private_addSort(e.getKey(), e.getValue());
        }
    }

    List<String> getIncludeFields() {
        return includeFields;
    }

    public List<String> getNewIncludeFields() {
        return (includeFields == null) ? new ArrayList<>() : new ArrayList<>(includeFields);
    }

    public void setIncludeFields(String includeField) {
        default_fieldNotEmpty(includeField);
        this.includeFields = new ArrayList<>(1);
        this.includeFields.add(includeField);
    }

    public void setIncludeFields(String... includeFields) {
        int length = default_getArrayLength(includeFields);
        if(length == 0) return;
        this.includeFields = new ArrayList<>(length);
        for (String f : includeFields) {
            default_fieldNotEmpty(f);
            this.includeFields.add(f);
        }
    }

    public void setIncludeFields(Iterable<String> includeFields) {
        if(includeFields instanceof List) {
            for (String f : includeFields) default_fieldNotEmpty(f);
            this.includeFields = (List<String>) includeFields;
            return;
        }
        int size = default_getIterableSize(includeFields);
        if(size == 0) return;
        this.includeFields = new ArrayList<>(size);
        for (String f : includeFields) {
            default_fieldNotEmpty(f);
            this.includeFields.add(f);
        }
    }

    List<String> getExcludeFields() {
        return excludeFields;
    }

    public List<String> getNewExcludeFields() {
        return (excludeFields == null) ? new ArrayList<>() : new ArrayList<>(excludeFields);
    }

    public void setExcludeFields(String excludeField) {
        default_fieldNotEmpty(excludeField);
        this.excludeFields = new ArrayList<>(1);
        this.excludeFields.add(excludeField);
    }

    public void setExcludeFields(String... excludeFields) {
        int length = default_getArrayLength(excludeFields);
        if(length == 0) return;
        this.excludeFields = new ArrayList<>(length);
        for (String f : excludeFields) {
            default_fieldNotEmpty(f);
            this.excludeFields.add(f);
        }
    }

    public void setExcludeFields(Iterable<String> excludeFields) {
        if(excludeFields instanceof List) {
            for (String f : excludeFields) default_fieldNotEmpty(f);
            this.excludeFields = (List<String>) excludeFields;
            return;
        }
        int size = default_getIterableSize(excludeFields);
        if(size == 0) return;
        this.excludeFields = new ArrayList<>(size);
        for (String f : excludeFields) {
            default_fieldNotEmpty(f);
            this.excludeFields.add(f);
        }
    }

    Map<String, Aggregation> getAggregations() {
        return aggregations;
    }

    public Map<String, Aggregation> getNewAggregations() {
        return (aggregations == null) ? new HashMap<>() : new HashMap<>(aggregations);
    }

    @Note("Aggregation 通过静态的 of 方法 + lambda 表达式构建。")
    public void setAggregations(String field, Aggregation agg) {
        default_fieldNotEmpty(field);
        private_aggregationNotNull(agg);
        this.aggregations = new HashMap<>(2);
        this.aggregations.put(field, agg);
    }

    public void setAggregations(Map<String, Aggregation> aggregations) {
        if(aggregations == null || aggregations.isEmpty()) return;
        this.aggregations = new HashMap<>(MapEnum.calcInitSize(aggregations.size()));
        for (Map.Entry<String, Aggregation> e : aggregations.entrySet()) private_addAgg(e.getKey(), e.getValue());
    }

    public void setAggregations(OwnPairs<String, Aggregation> aggregations) {
        if(aggregations == null || aggregations.isEmpty()) return;
        this.aggregations = new HashMap<>(MapEnum.calcInitSize(aggregations.size()));
        for (Map.Entry<String, Aggregation> e : aggregations.entrySet()) private_addAgg(e.getKey(), e.getValue());
    }


    Map<String, HighlightField> getHighlights() {
        return highlights;
    }

    public Map<String, HighlightField> getNewHighlights() {
        return (highlights == null) ? new HashMap<>() : new HashMap<>(highlights);
    }

    @Note("HighlightField 通过它自己的静态 of 方法 + lambda 表达式实现")
    public void setHighlights(String field, HighlightField highlightField) {
        default_fieldNotEmpty(field);
        private_highlightFieldNotNull(highlightField);
        this.highlights = new HashMap<>(2);
        this.highlights.put(field, highlightField);
    }

    public void setHighlights(Map<String, HighlightField> highlights) {
        if(highlights == null || highlights.isEmpty()) return;
        this.highlights = new HashMap<>(MapEnum.calcInitSize(highlights.size()));
        for (Map.Entry<String, HighlightField> e : highlights.entrySet()) private_addHighlightField(e.getKey(), e.getValue());
    }

    public void setHighlights(OwnPairs<String, HighlightField> highlights) {
        if(highlights == null || highlights.isEmpty()) return;
        this.highlights = new HashMap<>(MapEnum.calcInitSize(highlights.size()));
        for (Map.Entry<String, HighlightField> e : highlights.entrySet()) private_addHighlightField(e.getKey(), e.getValue());
    }

    @Override
    public String toString() {
        return "indicesNames=" + indicesNames +
                ", timeout=" + timeout +
                ", analyzer=" + analyzer +
                ", from=" + from +
                ", size=" + size +
                ", sorts=" + sorts +
                ", includeFields=" + includeFields +
                ", excludeFields=" + excludeFields +
                ", aggregations=" + aggregations +
                ", highlights=" + highlights;
    }

    /* **********************************************************************************
     *
     *          链式方法
     *
     * *********************************************************************************
     * */

    public Search indicesNames(String indicesName) {
        this.setIndicesNames(indicesName);
        return this;
    }

    public Search indicesNames(String[] indicesNames) {
        this.setIndicesNames(indicesNames);
        return this;
    }

    public Search indicesNames(Iterable<String> indicesNames) {
        this.setIndicesNames(indicesNames);
        return this;
    }

    public Search timeout(String timeout) {
        this.setTimeout(timeout);
        return this;
    }

    public Search timeout(int timeout, Timeunit timeunit) {
        this.setTimeout(timeout, timeunit);
        return this;
    }

    public Search from(int from) {
        this.setFrom(from);
        return this;
    }

    public Search size(int size) {
        this.setSize(size);
        return this;
    }

    public Search analyzer(String analyzer) {
        this.setAnalyzer(analyzer);
        return this;
    }

    public Search sorts(String field, SortOrder order) {
        this.setSorts(field, order);
        return this;
    }

    public Search sorts(Map<String, SortOrder> fieldAndOrders) {
        this.setSorts(fieldAndOrders);
        return this;
    }

    public Search sorts(OwnPairs<String, SortOrder> fieldAndOrders) {
        this.setSorts(fieldAndOrders);
        return this;
    }

    public Search includes(String includeField) {
        this.setIncludeFields(includeField);
        return this;
    }

    public Search includes(String[] includeFields) {
        this.setIncludeFields(includeFields);
        return this;
    }

    public Search includes(Iterable<String> includeFields) {
        this.setIncludeFields(includeFields);
        return this;
    }


    public Search excludes(String excludeField) {
        this.setExcludeFields(excludeField);
        return this;
    }

    public Search excludes(String[] excludeFields) {
        this.setExcludeFields(excludeFields);
        return this;
    }

    public Search excludes(Iterable<String> excludeFields) {
        this.setExcludeFields(excludeFields);
        return this;
    }

    @Note("Aggregation 通过静态的 of 方法 + lambda 表达式构建。")
    public Search aggregations(String field, Aggregation agg) {
        this.setAggregations(field, agg);
        return this;
    }

    public Search aggregations(Map<String, Aggregation> aggregations) {
        this.setAggregations(aggregations);
        return this;
    }

    public Search aggregations(OwnPairs<String, Aggregation> aggregations) {
        this.setAggregations(aggregations);
        return this;
    }

    @Note("HighlightField 通过它自己的静态 of 方法 + lambda 表达式实现")
    public Search highlights(String field, HighlightField highlight) {
        this.setHighlights(field, highlight);
        return this;
    }

    public Search highlights(Map<String, HighlightField> highlights) {
        this.setHighlights(highlights);
        return this;
    }

    public Search highlights(OwnPairs<String, HighlightField> highlights) {
        this.setHighlights(highlights);
        return this;
    }

    /* **********************************************************************************
     *
     *          自定义方法
     *
     * *********************************************************************************
     * */

    public boolean usePaging() {
        return size >= from;
    }

    public boolean useAnalyzer() {
        return analyzer != null && !analyzer.isEmpty();
    }

    public boolean useSort() {
        return sorts != null && !sorts.isEmpty();
    }

    public boolean useSource() {
        return useIncludeFields() || useExcludeFields();
    }

    public boolean useIncludeFields() {
        return includeFields != null && !includeFields.isEmpty();
    }

    public boolean useExcludeFields() {
        return excludeFields != null && !excludeFields.isEmpty();
    }

    public boolean useAgg() {
        return aggregations != null && !aggregations.isEmpty();
    }

    public boolean useHighlight() {
        return highlights != null && !highlights.isEmpty();
    }

    public Search addIndicesName(String indicesName) {
        default_indicesNameNotEmpty(indicesName);
        private_initIndicesNames(1);
        this.indicesNames.add(indicesName);
        return this;
    }

    public Search addIndicesNames(String[] indicesNames) {
        int length = default_getArrayLength(indicesNames);
        if(length != 0) {
            private_initIndicesNames(length);
            for (String name : indicesNames) {
                default_indicesNameNotEmpty(name);
                this.indicesNames.add(name);
            }
        }
        return this;
    }

    public Search addIndicesNames(Iterable<String> indicesNames) {
        int size = default_getIterableSize(indicesNames);
        if(size != 0) {
            private_initIndicesNames(size);
            for (String name : indicesNames) {
                default_indicesNameNotEmpty(name);
                this.indicesNames.add(name);
            }
        }
        return this;
    }

    public Search modifyIndicesName(int index, String indicesName) {
        if(indicesNames == null || indicesNames.isEmpty() || default_indexCheck(index, indicesNames.size())) return this;
        default_indicesNameNotEmpty(indicesName);
        this.indicesNames.set(index, indicesName);
        return this;
    }

    public Search removeIndicesName(String indicesName) {
        if(indicesNames == null || indicesNames.isEmpty()) return this;
        default_removeString(indicesName, indicesNames);
        return this;
    }

    public Search removeIndicesName(int index) {
        if(indicesNames == null || indicesNames.isEmpty()) return this;
        if(default_indexCheck(index, indicesNames.size())) return this;
        this.indicesNames.remove(index);
        return this;
    }

    public Search removeIndicesNames(String[] indicesNames) {
        if(indicesNames == null || indicesNames.length == 0 || this.indicesNames == null || this.indicesNames.isEmpty()) return this;
        for (String name : indicesNames) default_removeString(name, this.indicesNames);
        return this;
    }

    public Search removeIndicesNames(Iterable<String> indicesNames) {
        if(indicesNames == null || this.indicesNames == null || this.indicesNames.isEmpty()) return this;
        for (String name : indicesNames) default_removeString(name, this.indicesNames);
        return this;
    }

    public int indicesNamesSize() {
        return (indicesNames == null) ? 0 : indicesNames.size();
    }

    public String getIndicesName(int index) {
        if(indicesNames == null || indicesNames.isEmpty()) return null;
        if(default_indexCheck(index, indicesNames.size())) return null;
        return indicesNames.get(index);
    }

    public boolean containsIndicesName(String indicesName) {
        if(indicesNames == null || indicesNames.isEmpty()) return false;
        if(indicesName == null || indicesName.isEmpty()) return false;
        return this.indicesNames.contains(indicesName);
    }


    public Search addSort(String field, SortOrder order) {
        default_fieldNotEmpty(field);
        private_orderNotNull(order);
        private_initSortList(1);
        this.sorts.add(SortOptions.of(e -> e.field(fn -> fn.field(field).order(order))));
        return this;
    }

    public Search addSorts(Map<String, SortOrder> fieldAndOrders) {
        if(fieldAndOrders == null || fieldAndOrders.isEmpty()) return this;
        private_initSortList(fieldAndOrders.size());
        for (Map.Entry<String, SortOrder> e : fieldAndOrders.entrySet()) private_addSort(e.getKey(), e.getValue());
        return this;
    }


    public Search addSorts(OwnPairs<String, SortOrder> fieldAndOrders) {
        if(fieldAndOrders == null || fieldAndOrders.isEmpty()) return this;
        private_initSortList(fieldAndOrders.size());
        for (Map.Entry<String, SortOrder> e : fieldAndOrders.entrySet()) private_addSort(e.getKey(), e.getValue());
        return this;
    }

    public Search modifySort(int index, String field, SortOrder order) {
        if(sorts == null || sorts.isEmpty()) return this;
        if(default_indexCheck(index, sorts.size())) return this;
        default_fieldNotEmpty(field);
        private_orderNotNull(order);
        this.sorts.set(index, SortOptions.of(fn -> fn.field(f -> f.field(field).order(order))));
        return this;
    }

    public Search clearSorts() {
        if(sorts != null && !sorts.isEmpty()) this.sorts.clear();
        return this;
    }

    public Search removeSort(String field, SortOrder order) {
        if(sorts != null && !sorts.isEmpty()) private_remove(field, order);
        return this;
    }

    public Search removeSort(int index) {
        if(sorts != null && !sorts.isEmpty() && !default_indexCheck(index, sorts.size())) this.sorts.remove(index);
        return this;
    }

    public Search removeSorts(Map<String, SortOrder> fieldAndOrders) {
        if(fieldAndOrders == null || fieldAndOrders.isEmpty()) return this;
        if(sorts != null && !sorts.isEmpty()) {
            for (Map.Entry<String, SortOrder> e : fieldAndOrders.entrySet()) private_remove(e.getKey(), e.getValue());
        }
        return this;
    }

    public Search removeSorts(OwnPairs<String, SortOrder> fieldAndOrders) {
        if(fieldAndOrders == null || fieldAndOrders.isEmpty()) return this;
        if(sorts == null || sorts.isEmpty()) return this;
        for (Map.Entry<String, SortOrder> e : fieldAndOrders.entrySet()) private_remove(e.getKey(), e.getValue());
        return this;
    }

    public SortOptions getSort(int index) {
        if(sorts == null || sorts.isEmpty()) return null;
        if(default_indexCheck(index, sorts.size())) return null;
        return sorts.get(index);
    }

    public int sortSize() {
        if(sorts == null || sorts.isEmpty()) return 0;
        return sorts.size();
    }

    public boolean containsSort(String field, SortOrder order) {
        if(sorts == null || sorts.isEmpty()) return false;
        if(field == null || field.isEmpty() || order == null) return false;
        return private_containsSort(field, order);
    }


    public Search addInclude(String field) {
        default_fieldNotEmpty(field);
        private_initIncludeList(1);
        this.includeFields.add(field);
        return this;
    }

    public Search addIncludes(String[] fields) {
        int length = default_getArrayLength(fields);
        if(length == 0) return this;
        private_initIncludeList(length);
        for (String f : fields) {
            default_fieldNotEmpty(f);
            this.includeFields.add(f);
        }
        return this;
    }

    public Search addIncludes(Iterable<String> fields) {
        int size = default_getIterableSize(fields);
        if(size == 0) return this;
        private_initIncludeList(size);
        for (String f : fields) {
            default_fieldNotEmpty(f);
            this.includeFields.add(f);
        }
        return this;
    }


    public Search modifyInclude(int index, String field) {
        if(includeFields == null || includeFields.isEmpty()) return this;
        if(default_indexCheck(index, includeFields.size())) return this;
        default_fieldNotEmpty(field);
        this.includeFields.set(index, field);
        return this;
    }

    public Search clearIncludes() {
        if(includeFields == null || includeFields.isEmpty()) return this;
        this.includeFields.clear();
        return this;
    }

    public Search removeInclude(String field) {
        if(includeFields == null || includeFields.isEmpty()) return this;
        default_removeString(field, includeFields);
        return this;
    }

    public Search removeInclude(int index) {
        if(includeFields == null || includeFields.isEmpty()) return this;
        if(default_indexCheck(index, includeFields.size())) return this;
        this.includeFields.remove(index);
        return this;
    }

    public Search removeIncludes(String[] fields) {
        if(fields == null || fields.length == 0) return this;
        if(includeFields == null || includeFields.isEmpty()) return this;
        for (String f : fields) default_removeString(f, includeFields);
        return this;
    }

    public Search removeIncludes(Iterable<String> fields) {
        if(fields == null) return this;
        if(includeFields == null || includeFields.isEmpty()) return this;
        for (String f : fields) default_removeString(f, includeFields);
        return this;
    }

    public boolean containsInclude(String field) {
        if(includeFields == null || includeFields.isEmpty()) return false;
        if(field == null || field.isEmpty()) return false;
        return includeFields.contains(field);
    }

    public int getIncludeSize() {
        return (includeFields == null) ? 0 : includeFields.size();
    }

    public String getInclude(int index) {
        if(includeFields == null || includeFields.isEmpty()) return null;
        if(default_indexCheck(index, includeFields.size())) return null;
        return includeFields.get(index);
    }


    public Search addExclude(String field) {
        default_fieldNotEmpty(field);
        private_initExcludeList(1);
        this.excludeFields.add(field);
        return this;
    }

    public Search addExcludes(String[] fields) {
        int length = default_getArrayLength(fields);
        if(length == 0) return this;
        private_initExcludeList(length);
        for (String f : fields) {
            default_fieldNotEmpty(f);
            this.excludeFields.add(f);
        }
        return this;
    }

    public Search addExcludes(Iterable<String> fields) {
        int size = default_getIterableSize(fields);
        if(size == 0) return this;
        private_initExcludeList(size);
        for (String f : fields) {
            default_fieldNotEmpty(f);
            this.excludeFields.add(f);
        }
        return this;
    }

    public Search modifyExclude(int index, String field) {
        if(excludeFields == null || excludeFields.isEmpty()) return this;
        if(default_indexCheck(index, excludeFields.size())) return this;
        default_fieldNotEmpty(field);
        this.excludeFields.set(index, field);
        return this;
    }

    public Search clearExcludes() {
        if(excludeFields == null || excludeFields.isEmpty()) return this;
        this.excludeFields.clear();
        return this;
    }

    public Search removeExclude(String field) {
        if(excludeFields == null || excludeFields.isEmpty()) return this;
        default_removeString(field, excludeFields);
        return this;
    }

    public Search removeExclude(int index) {
        if(excludeFields == null || excludeFields.isEmpty()) return this;
        if(default_indexCheck(index, excludeFields.size())) return this;
        this.excludeFields.remove(index);
        return this;
    }

    public Search removeExcludes(String[] fields) {
        if(fields == null || fields.length == 0) return this;
        if(excludeFields == null || excludeFields.isEmpty()) return this;
        for (String f : fields) default_removeString(f, excludeFields);
        return this;
    }

    public Search removeExcludes(Iterable<String> fields) {
        if(fields == null) return this;
        if(excludeFields == null || excludeFields.isEmpty()) return this;
        for (String f : fields) default_removeString(f, excludeFields);
        return this;
    }

    public boolean containsExclude(String field) {
        if(excludeFields == null || excludeFields.isEmpty()) return false;
        if(field == null || field.isEmpty()) return false;
        return excludeFields.contains(field);
    }

    public int getExcludeSize() {
        return (excludeFields == null) ? 0 : excludeFields.size();
    }

    public String getExclude(int index) {
        if(excludeFields == null || excludeFields.isEmpty()) return null;
        if(default_indexCheck(index, excludeFields.size())) return null;
        return excludeFields.get(index);
    }


    @Note("Aggregation 通过静态的 of 方法 + lambda 表达式构建。")
    public Search addAgg(String field, Aggregation agg) {
        default_fieldNotEmpty(field);
        private_aggregationNotNull(agg);
        private_initAggregationMap(1);
        this.aggregations.put(field, agg);
        return this;
    }

    public Search addAggs(Map<String, Aggregation> aggs) {
        if(aggs == null || aggs.isEmpty()) return this;
        private_initAggregationMap(aggs.size());
        for (Map.Entry<String, Aggregation> e : aggs.entrySet()) private_addAgg(e.getKey(), e.getValue());
        return this;
    }

    public Search addAggs(OwnPairs<String, Aggregation> aggs) {
        if(aggs == null || aggs.isEmpty()) return this;
        private_initAggregationMap(aggs.size());
        for (Map.Entry<String, Aggregation> e : aggs.entrySet()) private_addAgg(e.getKey(), e.getValue());
        return this;
    }

    public Search clearAggs() {
        if(aggregations == null || aggregations.isEmpty()) return this;
        this.aggregations.clear();
        return this;
    }

    public Search removeAgg(String field) {
        if(aggregations == null || aggregations.isEmpty()) return this;
        this.aggregations.remove(field);
        return this;
    }

    public Search removeAgg(String[] fields) {
        if(aggregations == null || aggregations.isEmpty()) return this;
        if(fields == null || fields.length == 0) return this;
        for (String f : fields) this.aggregations.remove(f);
        return this;
    }

    public Search removeAgg(Iterable<String> fields) {
        if(aggregations == null || aggregations.isEmpty()) return this;
        if(fields == null) return this;
        for (String f : fields) this.aggregations.remove(f);
        return this;
    }
    
    public Aggregation getAgg(String field) {
        if(aggregations == null || aggregations.isEmpty()) return null;
        if(field == null || field.isEmpty()) return null;
        return aggregations.get(field);
    }
    
    public boolean containsAgg(String field) {
        if(aggregations == null || aggregations.isEmpty()) return false;
        if(field == null || field.isEmpty()) return false;
        return this.aggregations.containsKey(field);
    }
    
    public int aggSize() {
        return (aggregations == null) ? 0 : aggregations.size();
    }

    @Note("Highlight 通过它自己的静态 of 方法 + lambda 表达式实现")
    public Search addHighlight(String field, HighlightField highlight) {
        default_fieldNotEmpty(field);
        private_highlightFieldNotNull(highlight);
        private_initHighlightMap(1);
        this.highlights.put(field, highlight);
        return this;
    }

    public Search addHighlights(Map<String, HighlightField> highlights) {
        if(highlights == null || highlights.isEmpty()) return this;
        private_initHighlightMap(highlights.size());
        for (Map.Entry<String, HighlightField> e : highlights.entrySet()) private_addHighlightField(e.getKey(), e.getValue());
        return this;
    }

    public Search addHighlights(OwnPairs<String, HighlightField> highlights) {
        if(highlights == null || highlights.isEmpty()) return this;
        private_initHighlightMap(highlights.size());
        for (Map.Entry<String, HighlightField> e : highlights.entrySet()) private_addHighlightField(e.getKey(), e.getValue());
        return this;
    }

    public Search clearHighlight() {
        if(highlights != null && !highlights.isEmpty()) this.highlights.clear();
        return this;
    }

    public Search removeHighlight(String field) {
        if(highlights == null || highlights.isEmpty()) return this;
        this.highlights.remove(field);
        return this;
    }

    public Search removeHighlights(String[] fields) {
        if(fields == null || fields.length == 0) return this;
        if(highlights != null && !highlights.isEmpty()) for (String f : fields) this.highlights.remove(f);
        return this;
    }

    public Search removeHighlights(Iterable<String> fields) {
        if(fields == null) return this;
        if(highlights != null && !highlights.isEmpty()) for (String f : fields) this.highlights.remove(f);
        return this;
    }

    public HighlightField getHighlight(String field) {
        if(highlights == null || highlights.isEmpty()) return null;
        if(field == null || field.isEmpty()) return null;
        return highlights.get(field);
    }

    public boolean containsHighlight(String field) {
        if(highlights == null || highlights.isEmpty()) return false;
        if(field == null || field.isEmpty()) return false;
        return this.highlights.containsKey(field);
    }

    public int highlightSize() {
        return (highlights == null) ? 0 : highlights.size();
    }

    /* **********************************************************************************
     *
     *          私有逻辑
     *
     * *********************************************************************************
     * */

    @Note("索引名称的非空校验")
    static void default_indicesNameNotEmpty(String indices) {
        if(indices == null || indices.isEmpty()) throw new NullPointerException("索引名称不能为空");
    }


    @Note("初始化索引名称列表")
    private void private_initIndicesNames(int initSize) {
        if(indicesNames == null) this.indicesNames = new ArrayList<>(initSize);
    }

    @Note("字段名称的非空校验")
    static void default_fieldNotEmpty(String field) {
        if(field == null || field.isEmpty()) throw new NullPointerException("字段名称不能为空");
    }

    @Note("获取数组容器的容量")
    static int default_getArrayLength(String[] array) {
        return (array == null) ? 0 : array.length;
    }

    @Note("获取可迭代容器的容量")
    static int default_getIterableSize(Iterable<String> iterable) {
        return RowKit.getIterableSize(iterable);
    }

    @Note("排序规则的非空校验")
    private static void private_orderNotNull(SortOrder order) {
        if(order == null) throw new NullPointerException("排序规则不能为 null");
    }

    @Note("初始化排序规则列表")
    private void private_initSortList(int initSize) {
        if(sorts == null) this.sorts = new ArrayList<>(initSize);
    }

    @Note("移除排序规则的逻辑")
    private void private_remove(String field, SortOrder order) {
        Iterator<SortOptions> iterator = sorts.iterator();
        while (iterator.hasNext()) {
            SortOptions options = iterator.next();
            FieldSort fs = options.field();
            if(Objects.equals(fs.field(), field) && order == fs.order()) {
                iterator.remove();
            }
        }
    }


    @Note("判断列表中是否存在指定的 【字段 - 排序规则】 的逻辑")
    private boolean private_containsSort(String field, SortOrder order) {
        for (SortOptions options : sorts) {
            FieldSort fs = options.field();
            if(Objects.equals(fs.field(), field) && order == fs.order()) return true;
        }
        return false;
    }


    @Note("批量添加排序规则时，添加每一个的逻辑")
    private void private_addSort(String field, SortOrder order) {
        default_fieldNotEmpty(field);
        private_orderNotNull(order);
        this.sorts.add(SortOptions.of(fn -> fn.field(f -> f.field(field).order(order))));
    }

    @Note("初始化显示字段列表")
    private void private_initIncludeList(int initSize) {
        if(includeFields == null) this.includeFields = new ArrayList<>(initSize);
    }

    @Note("初始化禁止显示字段列表")
    private void private_initExcludeList(int initSize) {
        if(excludeFields == null) this.excludeFields = new ArrayList<>(initSize);
    }

    @Note("判断所以是否是错误的索引")
    boolean default_indexCheck(int index, int size) {
        return index < 0 || index >= size;
    }

    @Note("聚合函数实例的非空校验")
    private static void private_aggregationNotNull(Aggregation agg) {
        if(agg == null) throw new NullPointerException("聚合函数实例为 null");
    }

    @Note("初始化聚合函数集合")
    private void private_initAggregationMap(int initSize) {
        if(aggregations == null) this.aggregations = new HashMap<>(MapEnum.calcInitSize(initSize));
    }

    @Note("批量添加聚合函数时，添加每一个的逻辑")
    private void private_addAgg(String field, Aggregation agg) {
        default_fieldNotEmpty(field);
        private_aggregationNotNull(agg);
        this.aggregations.put(field, agg);
    }


    @Note("高亮实例的非空校验")
    private static void private_highlightFieldNotNull(HighlightField highlight) {
        if(highlight == null) throw new NullPointerException("高亮显示实例为 null");
    }

    @Note("初始化高亮显示的集合")
    private void private_initHighlightMap(int initSize) {
        if(highlights == null) this.highlights = new HashMap<>(MapEnum.calcInitSize(initSize));
    }

    @Note("批量高亮显示实例时，添加每一个的逻辑")
    private void private_addHighlightField(String field, HighlightField highlight) {
        default_fieldNotEmpty(field);
        private_highlightFieldNotNull(highlight);
        this.highlights.put(field, highlight);
    }

    @Note("移除列表中的元素前，先判断元素是否为空，是则不必循环判断了")
    static void default_removeString(String s, List<String> list) {
        if(s == null || s.isEmpty()) return;
        list.remove(s);
    }
}
